#include "database/connection.h"
#include "files/file.h"

#include <iostream>
#include <sqlite3.h>

namespace Tvimeg
{
	Connection::Connection(const File& databaseFile)
		:	database(NULL), currentQuery(NULL), hasRow(false)
	{
		if (sqlite3_open(databaseFile.GetPath().c_str(), &database) != SQLITE_OK)
		{
			PrintError("Failed to open db: " + databaseFile.GetPath());
		}
	}

	Connection::~Connection(void)
	{
		CloseDatabase();
	}

	void Connection::FinalizeQuery(void)
	{
		if (currentQuery)
		{
			if (sqlite3_finalize(currentQuery) != SQLITE_OK)
			{
				PrintError("Failed to finalize query.");
			}

			currentQuery = NULL;
		}

		hasRow = false;
	}

	void Connection::CloseDatabase(void)
	{
		FinalizeQuery();

		if (database)
		{
			int result = sqlite3_close(database);
			database = NULL;

			if (result != SQLITE_OK)
			{
				PrintError("Failed to close database.");
			}
		}
	}

	bool Connection::ExecuteQuery(const std::string& query)
	{
		FinalizeQuery();

		if (database && PrepareQuery(query) && ExecuteQuery())
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	bool Connection::ExecuteQuery(void)
	{
		if (currentQuery)
		{
			const bool result = sqlite3_step(currentQuery) == SQLITE_DONE;

			if (!result)
			{
				PrintError("Failed to execute query.");
			}

			FinalizeQuery();
			return result;
		}
		else
		{
			return false;
		}
	}

	bool Connection::PrepareQuery(const std::string& query)
	{
		FinalizeQuery();

		if (database)
		{
			const bool result = sqlite3_prepare_v2(database, query.c_str(), query.length() + 1, &currentQuery, NULL) == SQLITE_OK;

			if (!result)
			{
				PrintError("Failed to prepare query: " + query);
			}

			return result;
		}
		else
		{
			return false;
		}
	}

	bool Connection::NextRow(void)
	{
		if (currentQuery)
		{
			const int result = sqlite3_step(currentQuery);
			hasRow = result == SQLITE_ROW;

			if (!hasRow && result != SQLITE_DONE)
			{
				PrintError("Failed to get next row.");
			}

			return hasRow;
		}
		else
		{
			return false;
		}
	}

	void Connection::BindText(const int parameter, const std::string& text)
	{
		if (currentQuery)
		{
			if (sqlite3_bind_text(currentQuery, parameter, text.c_str(), text.length(), SQLITE_STATIC) != SQLITE_OK)
			{
				PrintError("Failed to bind text: " + text);
			}
		}
	}

	void Connection::BindInt(const int parameter, const int value)
	{
		if (currentQuery)
		{
			if (sqlite3_bind_int(currentQuery, parameter, value) != SQLITE_OK)
			{
				PrintError("Failed to bind int.");
			}
		}
	}

	void Connection::BindFloat(const int parameter, const float value)
	{
		if (currentQuery)
		{
			if (sqlite3_bind_double(currentQuery, parameter, value) != SQLITE_OK)
			{
				PrintError("Failed to bind float.");
			}
		}
	}

	int Connection::GetColumnCount(void) const
	{
		if (hasRow && currentQuery)
		{
			return sqlite3_column_count(currentQuery);
		}
		else
		{
			return 0;
		}
	}

	std::string Connection::GetString(const int column) const
	{
		if (hasRow && currentQuery)
		{
			return reinterpret_cast<const char*>(sqlite3_column_text(currentQuery, column));
		}
		else
		{
			return "";
		}
	}

	int Connection::GetInt(const int column) const
	{
		if (hasRow && currentQuery)
		{
			return sqlite3_column_int(currentQuery, column);
		}
		else
		{
			return 0;
		}
	}

	std::string Connection::GetString(const std::string& query, const int column)
	{
		std::string result;

		if (PrepareQuery(query) && NextRow())
		{
			result = GetString(column);
		}

		FinalizeQuery();
		return result;
	}

	int Connection::GetInt(const std::string& query, const int column)
	{
		int result = 0;

		if (PrepareQuery(query) && NextRow())
		{
			result = GetInt(column);
		}

		FinalizeQuery();
		return result;
	}

	int Connection::GetLastId(void) const
	{
		if (database)
		{
			return sqlite3_last_insert_rowid(database);
		}
		else
		{
			return 0;
		}
	}

	void Connection::PrintError(const std::string& error) const
	{
		std::cerr << "sqlite Error: ";

		if (database)
		{
			std::cerr << sqlite3_errmsg(database) << std::endl;
		}

		std::cerr << error << std::endl;
	}
}
